package net.lindeer.lang

class Environment(private val parent: Environment? = null) {
    private val values = mutableMapOf<String, Any?>()

    fun define(name: String, value: Any?) {
        values[name] = value
    }

    fun get(name: Token): Any? {
        val key = name.lexeme
        if (values.containsKey(key)) {
            return values[key]
        }
        if (parent != null) {
            return parent.get(name)
        }
        throw RuntimeError(name, "undefined variable '${name.lexeme}'!")
    }

    fun assign(name: Token, value: Any?) {
        val key = name.lexeme
        if (values.containsKey(key)) {
            values[key] = value
            return
        }
        if (parent != null) {
            parent.assign(name, value)
            return
        }

        throw RuntimeError(name, "Undefine variable '$key'!")
    }

    fun assignAt(depth: Int, token: Token, value: Any?) {
        ancestor(depth).values[token.lexeme] = value
    }

    fun getAt(depth: Int, name: String): Any? {
        return ancestor(depth).values[name]
    }

    private fun ancestor(depth: Int): Environment {
        var env = this
        for (i in 0 until depth) {
            env = env.parent!!
        }
        return env
    }

    override fun toString(): String {
        return "$values --> $parent"
    }
}
